#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <signal.h>
#include <netdb.h>
#include <string>
#include <pthread.h>
#include <math.h>
#include <map>
#include <errno.h>

#include "definitions.hpp"

using namespace std;

map<uint32_t,bool> Seq_Map;
map<uint32_t,bool>::iterator sit;

uint32_t mini = 1;


void *udp_thread_server_func(void* d2){
    
    //cout<<"UDP server thread"<<endl;
    pthread_mutex_lock(&synch_lock);
    pthread_cond_wait(&wakeup_udp,&synch_lock);
    pthread_mutex_unlock(&synch_lock);
    int portno;
    socklen_t clilen;
    struct sockaddr_in serv_addr, cli_addr;
    int yes=1;
    //cout<<"UDP server thread"<<endl;
    
    int sockfd;
    
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
    if(sockfd < 0){
        error("ERROR opening socket");
    }
    
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                   sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }
    
    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = d1->getPortNum();
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno+1);
    
    //Binding Socket 
    if (bind(sockfd, (struct sockaddr *) &serv_addr,
             sizeof(serv_addr)) < 0) 
        error("ERROR on binding");
    
    //cout<<"udp_thread_server"<<endl;
    
    int n=0;
    clilen = sizeof(cli_addr);
	uint32_t Seq_Number = 0;
    
	unsigned char packet_buf[max_pkt_size];
	bzero(packet_buf,max_pkt_size);

    while(file_complete_flag != 1) {
        if(sock_ready(sockfd)){            
            if (file_complete_flag==1) {
                break;
            }
            n = recvfrom(sockfd,&packet_buf[0],max_pkt_size,0,(struct sockaddr *) &cli_addr,&clilen);
            if (n <0) {
                error("recvfrom"); 
                continue;
            }
            // write the packet to the file
            memcpy(&Seq_Number,&packet_buf[max_load_size],4);
            // cout<<"seq_number ..........."<<Seq_Number<<endl;
            if(Seq_Number > num_packets){
                continue;
            }
            if(Seq_Map.find(Seq_Number) != Seq_Map.end())
            {
                continue;
            }
            // FILE* fp1 = fopen("Output","wb+");
            fseek(fp,(Seq_Number-1)*1450,SEEK_SET);
            if(Seq_Number == (num_packets)){
                fwrite(&packet_buf[0],last_pack,1,fp);
            } else {
                fwrite(&packet_buf[0],max_load_size,1,fp);
            }
            fflush(fp);
            Seq_Map[Seq_Number] = 1;
            //fclose(fp);
        }
	}
    double endTime1 = 0;
    endTime1 = mytime();
    printf("Endtime is %12.3f\n",endTime1);

    close(sockfd);    
	fflush(fp);
    fclose(fp);
	//fflush(fp1);
    //fclose(fp1);
    cout<<"udp_thread_server exiting"<<endl;
    pthread_exit(NULL);
}

void *udp_dup_func(void* d2){
    
    //cout<<"UDP duplicate thread"<<endl;
    int portno;
    struct sockaddr_in serv_addr;
    int yes=1;
    struct hostent *server;  
    //cout<<"UDP duplicate thread"<<endl;
    
    int sockfd;
    
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
    if(sockfd < 0){
        error("ERROR opening socket");
    }
    
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                   sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }
    
    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = d1->getPortNum();
	server = gethostbyname((char *)((d1->getHostname()).c_str())); 
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }
    
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr, 
          (char *)&serv_addr.sin_addr.s_addr,
          server->h_length);
    serv_addr.sin_port = htons(portno+2);
    
    //Binding Socket 
    if (bind(sockfd, (struct sockaddr *) &serv_addr,
             sizeof(serv_addr)) < 0) 
        error("ERROR on binding");
    
    // FILE* fp = d1->getfilePointer();
    
    //cout<<"creating udp_thread_duplicate send thread"<<endl;
    
    
    pthread_t udp_dup_thread;
    pthread_attr_t attr_udp_dup;
    int thrd_udp_dup=0;    
    pthread_attr_init(&attr_udp_dup);
    pthread_attr_setdetachstate(&attr_udp_dup, PTHREAD_CREATE_JOINABLE);
    //cout<<"Before pthread create udpSFASFDAFD"<<endl;
    thrd_udp_dup=pthread_create(&udp_dup_thread,&attr_udp_dup,udp_dup_send_func,(void *)sockfd);
    if(thrd_udp_dup){
        cerr<<"Error; return code from thread tcp create call is"<<thrd_udp_dup<<endl;
        exit(-1);
    } 
    
    pthread_attr_destroy(&attr_udp_dup);
    
    int n;
    unsigned char dupBuffer[2500];
    while(1) {
        usleep(1000);
        int pt = 0;
        bzero(dupBuffer, 2500);
        
        uint32_t y;
        // cout<<"num_packets is "<<num_packets<<endl;
        for(y = mini;y<=num_packets;y = y+1)
        {
            // cout<<"checking y"<<y<<endl;
            sit = Seq_Map.find(y);
            if(sit == Seq_Map.end()){	
                memcpy(&dupBuffer[pt],&y,4);
                pt = pt+4;		   
                if(pt >= 2500){
                    break;
                }
            }			
        }
        // write the buffer into UDP socket. 
        // cout<<"writing the buffer to "<<endl;
        n = sendto(sockfd,dupBuffer,2500,0,(struct sockaddr *) &serv_addr,sizeof(serv_addr));
        if (n<0) {
            error("writenak");
        }
        
        
        uint32_t k;
        for(k = mini;k<=num_packets;k++)
        {
            sit = Seq_Map.find(k);
            if(sit == Seq_Map.end()){
                mini = k;
                cout<<"setting to mini" <<mini<<endl;
                break;
            }			
        }
        
        if(k == (num_packets+1)){
            // we have received all the packets 
            // signal TCP, set flag and die.
            //cout<<"I am exitin coz " << num_packets <<" y is " <<k<<endl;
            pthread_mutex_lock(&sleep_lock);
            pthread_cond_signal(&sleep_cond);
            pthread_mutex_unlock(&sleep_lock);
            file_complete_flag = 1;
            break;		
        }
        
        
        
    }
    
    
    close(sockfd); 
    void* status_udp;
	int thrd_udp=pthread_join(udp_dup_thread,&status_udp);
    if(thrd_udp){
        cerr<<"Error; return code from thread tcp join call is "<<thrd_udp<<endl;
        exit(-1);
    }
    cout<<"udp_duplicate_server exiting"<<endl;
    
    pthread_exit(NULL);
    
}

void *udp_dup_send_func(void* d2){
    int portno;
    socklen_t clilen;
    struct sockaddr_in serv_addr, cli_addr;
    int yes=1;
    //cout<<"UDP server thread"<<endl;
    
    int sockfd;
    
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
    if(sockfd < 0){
        error("ERROR opening socket");
    }
    
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                   sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }
    
    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = d1->getPortNum();
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno+2);
    
    //Binding Socket 
    if (bind(sockfd, (struct sockaddr *) &serv_addr,
             sizeof(serv_addr)) < 0) 
        error("ERROR on binding");
    
    //cout<<"udp_thread_server"<<endl;
    
    int n=0;
    clilen = sizeof(cli_addr);
	uint32_t Seq_Number;
    unsigned char packet_buf[max_pkt_size];
	bzero(packet_buf,max_pkt_size);

    while(file_complete_flag != 1) {
        /*     if(sock_ready(sockfd)){            
         if (file_complete_flag==1) {
         break;
         }
         */       n = recvfrom(sockfd,packet_buf,max_pkt_size,0,(struct sockaddr *) &cli_addr,&clilen);
        if (n <0) {
            error(".........................recvfrom"); 
            continue;
        }
        // write the packet to the file
        memcpy(&Seq_Number,&packet_buf[max_load_size],4);
        //cout<<"seq_number in dup UDP "<<Seq_Number<<endl;
        if(Seq_Number > num_packets){
            continue;
        }
        if(Seq_Map.find(Seq_Number) != Seq_Map.end())
        {
            continue;
        }
        //FILE* fp1 = fopen("Output","wb+");

        fseek(fp1,(Seq_Number-1)*1450,SEEK_SET);
        if(Seq_Number == (num_packets)){
            fwrite(&packet_buf[0],last_pack,1,fp1);
        } else {
            fwrite(&packet_buf[0],max_load_size,1,fp1);
        }    
        fflush(fp1);
        Seq_Map[Seq_Number] = 1;
        //fclose(fp1);
        //     }
	}
	fflush(fp1);
    fclose(fp1);
	//fflush(fp);
    //fclose(fp);
    //cout<<"udp_duplicate_server send exiting"<<endl;   
    pthread_exit(NULL);
}


//------------------------------------------------------------------f1

